﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using HTML5Converter.Entities;


namespace HTML5Converter
{
    /// <summary>
    /// Interaction logic for UC_Converter.xaml
    /// </summary>
    public partial class UC_Converter : UserControl
    {
        string strSelectedFilePath = string.Empty;
        List<string> lstOriginalFile = new List<string>();
        List<string> lstTemp = new List<string>();
        int HTMLStartIndex;
        int HTMLEndIndex;
        int HeadStartIndex;
        int HeadEndIndex;
        int BodyStartindex;
        int BodyEndIndex;
        int FormStartIndex;
        int FormEndIndex;
        int RowsInOriginal = 0;

        public UC_Converter()
        {
            InitializeComponent();
            lstLocalReplace = lstReplace;
            lstLocalAddContnet = LstAddContent;
            Loaded += (sender, eventArgs) =>
            {
                ScrollViewer s = FindDescendant<ScrollViewer>(txtModified);
                ScrollViewer d = FindDescendant<ScrollViewer>(txtOriginal);

                s.ScrollChanged += (sen, evt) =>
                {
                    d.ScrollToVerticalOffset(evt.VerticalOffset);
                    d.ScrollToHorizontalOffset(evt.HorizontalOffset);
                };

                d.ScrollChanged += (sen1, evt1) =>
                {
                    s.ScrollToHorizontalOffset(evt1.HorizontalOffset);
                    s.ScrollToVerticalOffset(evt1.VerticalOffset);
                };
            };
        }

        private async void btnBrowseFile_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.Forms.OpenFileDialog objFileDialog = new System.Windows.Forms.OpenFileDialog();
            objFileDialog.CheckFileExists = true;
            objFileDialog.CheckPathExists = true;
            objFileDialog.DefaultExt = "*.aspx";
            objFileDialog.Multiselect = false;
            objFileDialog.Filter = "*.aspx|";
            if (objFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                FileInfo fi = new FileInfo(objFileDialog.FileName);
                if (string.Compare(fi.Extension, ".aspx", true) == 0)
                {
                    if (!fi.IsReadOnly)
                    {
                        strSelectedFilePath = objFileDialog.FileName;
                    }
                    else
                    {
                        MessageBox.Show("Selected file not checkedout.\n Please check out.");
                    }
                }
                else
                {
                    MessageBox.Show("Not supported file selected.");
                    return;
                }
            }
            else
                return;

            using (StreamReader sr = new StreamReader(strSelectedFilePath))
            {
                string str = null;
                while ((str = sr.ReadLine()) != null)
                {
                    if (!string.IsNullOrEmpty(str))
                        lstOriginalFile.Add(str);
                    RowsInOriginal++;
                    txtOriginal.Text += str + Environment.NewLine;
                }
            }
            bool result = await Task.Factory.StartNew(() => MainWork());
            if (result)
                lstOriginalFile.ForEach(no => txtModified.Text += no + Environment.NewLine);
            if ((RowsInOriginal - lstOriginalFile.Count) > 1)
            {
                for (int i = 0; i < (RowsInOriginal - lstOriginalFile.Count) - 1; i++)
                {
                    txtModified.Text += Environment.NewLine;
                }
            }
        }

        public bool MainWork()
        {
            txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text = "Reading of file Completed" + Environment.NewLine);

            for (int i = 0; i < lstOriginalFile.Count; i++)
            {
                if (!lstOriginalFile[i].Trim().EndsWith(">") && i < lstOriginalFile.Count - 1)
                {
                    lstOriginalFile[i] = lstOriginalFile[i].Trim();
                    lstOriginalFile[i] += " " + lstOriginalFile[i + 1].Trim();
                    lstOriginalFile.RemoveAt(i + 1);
                    if (i != 0)
                        i--;
                }
            }
            txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "Removing unwanted spaces and proper end tags completed." + Environment.NewLine);



            #region Moving Page Directive

            GetStartEndIndexesOfTags(out HTMLStartIndex, out HTMLEndIndex, "html");

            string strPageDirective = lstOriginalFile.GetRange(0, HTMLStartIndex).SingleOrDefault(no => no.StartsWith("<%@ Page", StringComparison.CurrentCultureIgnoreCase) && no.ToLower().Contains("language=\"c#\""));
            if (!string.IsNullOrEmpty(strPageDirective))
            {
                lstOriginalFile.Remove(strPageDirective);
                lstOriginalFile.Insert(0, strPageDirective);
                txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "Page Directive moved to top Completed." + Environment.NewLine);
            }
            #endregion Moving Page Directive

            #region HTML5 DOCTYPE

            lstOriginalFile.Remove(lstOriginalFile.GetRange(0, GetHTMLTagIndex()).SingleOrDefault(no => no.Trim().StartsWith("<!DOCTYPE", StringComparison.InvariantCultureIgnoreCase)));

            lstOriginalFile.Insert(GetHTMLTagIndex(), "<!DOCTYPE HTML >");
            txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "DOCTYPE Changed to HTML5." + Environment.NewLine);
            #endregion HTML5 DOCTYPE

            #region Removing Meta tags in HEAD tags

            GetStartEndIndexesOfTags(out HeadStartIndex, out HeadEndIndex, "head");

            //if (HeadStartIndex > 0 && (HeadIndex > 0 && endHeadIndex > startHeadIndex))
            lstOriginalFile.GetRange(HeadStartIndex + 1, (HeadEndIndex - HeadStartIndex) - 1).Where(no => no.Trim().StartsWith("<meta", StringComparison.InvariantCultureIgnoreCase)).ToList<string>().ForEach(str => lstOriginalFile.Remove(str));
            txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "Meta tags removal completed." + Environment.NewLine);
            //else
            //    txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "Meta tags removal failed");
            #endregion Removing Meta tags in HEAD tags

            #region Moving Script to end of the page

            GetStartEndIndexesOfTags(out HeadStartIndex, out HeadEndIndex, "head");

            lstTemp = lstOriginalFile.GetRange(HeadStartIndex + 1, (HeadEndIndex - HeadStartIndex) - 1).Where(no => no.Trim().StartsWith("<script", StringComparison.InvariantCultureIgnoreCase) && no.ToLower().IndexOf("src=", StringComparison.InvariantCultureIgnoreCase) > 0).ToList<string>();

            lstTemp.ForEach(scr => lstOriginalFile.Remove(scr));

            GetStartEndIndexesOfTags(out BodyStartindex, out BodyEndIndex, "body");

            lstTemp.ForEach(scr => lstOriginalFile.Insert(BodyEndIndex, scr));
            BodyEndIndex += lstTemp.Count;
            lstTemp.Clear();
            txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "Moving Script to end of the page Completed." + Environment.NewLine);

            GetStartEndIndexesOfTags(out FormStartIndex, out FormEndIndex, "form");

            #region Adding async Keyword to all script files

            lstTemp = lstOriginalFile.GetRange(FormEndIndex + 1, (BodyEndIndex - FormEndIndex) - 1).Where(no => no.Trim().StartsWith("<script", StringComparison.InvariantCultureIgnoreCase) && no.ToLower().Contains("src=")).ToList<string>();

            lstTemp.ForEach(scr => lstOriginalFile.Remove(scr));
            BodyEndIndex -= lstTemp.Count;
            //lstTemp.ForEach(scr => lstOriginalFile.Insert(BodyEndIndex, scr.ToLower().Replace("<script", "<script async ")));
            //lstTemp.ForEach(scr => lstOriginalFile.Insert(BodyEndIndex, scr.Replace("/>", " async />")));
            //lstTemp.ForEach(scr => lstOriginalFile.Insert(BodyEndIndex, scr.Replace(">", " async />")));
            foreach (string str in lstTemp)
            {
                if (str.IndexOf("async ", StringComparison.InvariantCultureIgnoreCase) <= 0)
                {
                    lstOriginalFile.Insert(BodyEndIndex, str.Trim().Insert(str.Trim().IndexOf("<script ", StringComparison.CurrentCultureIgnoreCase) + "<script ".Length, "async "));
                }
            }

            lstTemp.Clear();
            txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "Adding async keyword to Script Completed." + Environment.NewLine);

            #endregion Adding async Keyword to all script files

            //}
            //else
            //    txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "Moving Script to end of the page failed"+Environment.NewLine);
            //}
            //else
            //    txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "Cannot find head tag");
            #endregion Moving Script to end of the page

            #region Replace and Remove

            foreach (ReplaceRemove objReplaceRemove in lstLocalReplace)
            {
                switch (objReplaceRemove.Where)
                {
                    case WhereEnum.BeforeHTML:

                        try
                        {
                            string strDirective = lstOriginalFile.GetRange(0, GetHTMLTagIndex() + 1).Where(no => no.Trim().StartsWith(objReplaceRemove.StartsWith, StringComparison.InvariantCultureIgnoreCase) && no.IndexOf(objReplaceRemove.Contains, StringComparison.InvariantCultureIgnoreCase) >= 0).SingleOrDefault();
                            string strTagName = string.Empty;
                            if (!string.IsNullOrEmpty(strDirective))
                            {
                                foreach (string str in strDirective.Trim().Split(new char[] { ' ' }))
                                {
                                    if (str.StartsWith("TagPrefix", StringComparison.InvariantCultureIgnoreCase))
                                        strTagName = "<" + str.Split(new char[] { '=' })[1].Replace("\"", string.Empty) + strTagName;
                                    else if (str.StartsWith("TagName", StringComparison.InvariantCultureIgnoreCase))
                                        strTagName += ":" + str.Split(new char[] { '=' })[1].Replace("\"", string.Empty);
                                }
                            }

                            switch (objReplaceRemove.Action)
                            {
                                case ActionEnum.Remove:
                                    if (objReplaceRemove.ReplaceRelativeTags && !string.IsNullOrEmpty(strDirective))
                                    {
                                        lstOriginalFile.RemoveAll(no => no == strDirective);
                                        lstOriginalFile.Where(no => no.Trim().StartsWith(strTagName, StringComparison.InvariantCultureIgnoreCase)).ToList().ForEach(s => lstOriginalFile[lstOriginalFile.IndexOf(s)] = objReplaceRemove.ReplaceWith);
                                        txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "Removal of page directive startswith " + objReplaceRemove.StartsWith + "and contains " + objReplaceRemove.Contains + " successful with replacing of assosiated tags." + Environment.NewLine);
                                    }
                                    else
                                    {
                                        lstOriginalFile.GetRange(0, GetHTMLTagIndex() + 1).RemoveAll(no => no.Trim().StartsWith(objReplaceRemove.StartsWith, StringComparison.InvariantCultureIgnoreCase) && no.IndexOf(objReplaceRemove.Contains, StringComparison.InvariantCultureIgnoreCase) >= 0);
                                        txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "Removal of page directive startswith " + objReplaceRemove.StartsWith + "and contains " + objReplaceRemove.Contains + "is Succesful" + Environment.NewLine);
                                    }
                                    break;
                                case ActionEnum.Replace:
                                    lstOriginalFile[lstOriginalFile.IndexOf(strDirective)] = objReplaceRemove.ReplaceWith;

                                    break;
                                case ActionEnum.Append:
                                    lstOriginalFile.GetRange(0, GetHTMLTagIndex()).Where(no => no.Trim().StartsWith(objReplaceRemove.StartsWith, StringComparison.InvariantCultureIgnoreCase) && no.IndexOf(objReplaceRemove.Contains, StringComparison.InvariantCultureIgnoreCase) > 0).ToList().ForEach(p => lstOriginalFile[lstOriginalFile.IndexOf(p)] = p.Replace("%>", objReplaceRemove.ReplaceWith + "%>"));
                                    break;
                            }
                        }

                        catch (Exception ex)
                        {
                            txtStatus.Dispatcher.InvokeAsync(() => txtStatus.Text += "Removal of page directive startswith " + objReplaceRemove.StartsWith + "and contains " + objReplaceRemove.Contains + "is Failed with exception" + ex.Message + Environment.NewLine);
                        }

                        break;
                    case WhereEnum.InsideHead:
                        GetStartEndIndexesOfTags(out HeadStartIndex, out HeadEndIndex, "head");
                        switch (objReplaceRemove.Action)
                        {
                            case ActionEnum.Remove:
                                lstOriginalFile.GetRange(HeadStartIndex, (HeadEndIndex - HeadStartIndex)).Where(no => no.StartsWith(objReplaceRemove.StartsWith, StringComparison.InvariantCultureIgnoreCase) && no.IndexOf(objReplaceRemove.Contains, StringComparison.InvariantCultureIgnoreCase) > 0).ToList().ForEach(n => lstOriginalFile.Remove(n));
                                break;
                            case ActionEnum.Replace:
                                lstOriginalFile.GetRange(HeadStartIndex, (HeadEndIndex - HeadStartIndex)).Where(no => no.StartsWith(objReplaceRemove.StartsWith, StringComparison.InvariantCultureIgnoreCase) && no.IndexOf(objReplaceRemove.Contains, StringComparison.InvariantCultureIgnoreCase) > 0).ToList().ForEach(n => lstOriginalFile[lstOriginalFile.IndexOf(n)] = objReplaceRemove.ReplaceWith);
                                break;
                            case ActionEnum.Append:
                                lstOriginalFile.GetRange(HeadStartIndex, (HeadStartIndex - HeadEndIndex)).Where(no => no.Trim().StartsWith(objReplaceRemove.StartsWith, StringComparison.InvariantCultureIgnoreCase) && no.IndexOf(objReplaceRemove.Contains, StringComparison.InvariantCultureIgnoreCase) > 0).ToList().ForEach(p => lstOriginalFile[lstOriginalFile.IndexOf(p)] = p.Insert(p.LastIndexOf(' '), " " + objReplaceRemove.ReplaceWith));
                                break;
                        }

                        break;
                    case WhereEnum.InsideBody:
                        GetStartEndIndexesOfTags(out BodyStartindex, out BodyEndIndex, "body");
                        switch (objReplaceRemove.Action)
                        {
                            case ActionEnum.Remove:
                                lstOriginalFile.GetRange(BodyStartindex, (BodyEndIndex - BodyStartindex)).Where(no => no.StartsWith(objReplaceRemove.StartsWith, StringComparison.InvariantCultureIgnoreCase) && no.IndexOf(objReplaceRemove.Contains, StringComparison.InvariantCultureIgnoreCase) > 0).ToList().ForEach(n => lstOriginalFile.Remove(n));
                                break;
                            case ActionEnum.Replace:
                                lstOriginalFile.GetRange(BodyStartindex, (BodyEndIndex - BodyStartindex)).Where(no => no.StartsWith(objReplaceRemove.StartsWith, StringComparison.InvariantCultureIgnoreCase) && no.IndexOf(objReplaceRemove.Contains, StringComparison.InvariantCultureIgnoreCase) > 0).ToList().ForEach(n => lstOriginalFile[lstOriginalFile.IndexOf(n)] = objReplaceRemove.ReplaceWith);
                                break;
                            case ActionEnum.Append:
                                lstOriginalFile.GetRange(BodyStartindex, (BodyEndIndex - BodyStartindex)).Where(no => no.Trim().StartsWith(objReplaceRemove.StartsWith, StringComparison.InvariantCultureIgnoreCase) && no.IndexOf(objReplaceRemove.Contains, StringComparison.InvariantCultureIgnoreCase) > 0).ToList().ForEach(p => lstOriginalFile[lstOriginalFile.IndexOf(p)] = p.Insert(p.LastIndexOf(' '), " " + objReplaceRemove.ReplaceWith));
                                break;
                        }

                        break;
                    default:
                        break;
                }
            }


            #endregion Replace and Remove

            #region Add Content

            foreach (AddContent objAddContent in lstLocalAddContnet)
            {
                switch (objAddContent.ContentType)
                {
                    case ContentTypeEnum.style:
                        GetStartEndIndexesOfTags(out HeadStartIndex, out HeadEndIndex, "head");
                        lstOriginalFile.Insert(HeadEndIndex, objAddContent.Content);
                        break;
                    case ContentTypeEnum.JavaScript:
                        GetStartEndIndexesOfTags(out FormStartIndex, out FormEndIndex, "form");
                        lstOriginalFile.Insert(FormEndIndex + 1, objAddContent.Content);
                        break;
                    case ContentTypeEnum.HTMLTag:
                        GetStartEndIndexesOfTags(out FormStartIndex, out FormEndIndex, "form");
                        lstOriginalFile.Insert(FormEndIndex, objAddContent.Content);
                        break;
                    case ContentTypeEnum.Other:
                        switch (objAddContent.Where)
                        {
                            case WhereEnum.BeforeHTML:
                                GetHTMLTagIndex();
                                lstOriginalFile.Insert(HTMLStartIndex, objAddContent.Content);
                                break;
                            case WhereEnum.InsideHead:
                                GetStartEndIndexesOfTags(out HeadStartIndex, out HeadEndIndex, "head");
                                lstOriginalFile.Insert(HeadEndIndex, objAddContent.Content);
                                break;
                            case WhereEnum.InsideBody:
                                GetStartEndIndexesOfTags(out FormStartIndex, out FormEndIndex, "form");
                                lstOriginalFile.Insert(FormEndIndex, objAddContent.Content);
                                break;
                            default:
                                break;
                        }
                        break;
                    default:
                        break;
                }
            }
            GetStartEndIndexesOfTags(out FormStartIndex, out FormEndIndex, "form");
            GetStartEndIndexesOfTags(out BodyStartindex, out BodyEndIndex, "body");
            lstOriginalFile.GetRange(FormEndIndex + 1, (BodyEndIndex - FormEndIndex) - 1).Where(no => no.Trim().StartsWith("<script", StringComparison.InvariantCultureIgnoreCase) && no.IndexOf("src=", StringComparison.InvariantCultureIgnoreCase) > 0 && no.IndexOf("async ", StringComparison.InvariantCultureIgnoreCase) <= 0).ToList().ForEach(str => lstOriginalFile[lstOriginalFile.IndexOf(str)] = str.Trim().Insert(str.Trim().IndexOf("<script ", StringComparison.CurrentCultureIgnoreCase) + "<script ".Length, "async "));

            #endregion Add Content


            return true;
        }

        public int GetHTMLTagIndex()
        {
            return lstOriginalFile.IndexOf(lstOriginalFile.Single(no => no.Trim().StartsWith("<html", StringComparison.InvariantCultureIgnoreCase)));
        }

        public void GetStartEndIndexesOfTags(out int startIndex, out int endIndex, string tag)
        {
            try
            {
                if (!string.IsNullOrEmpty(tag))
                {
                    startIndex = lstOriginalFile.IndexOf(lstOriginalFile.First(no => no.Trim().StartsWith("<" + tag, StringComparison.InvariantCultureIgnoreCase)));
                    endIndex = lstOriginalFile.IndexOf(lstOriginalFile.First(no => no.Trim().StartsWith("</" + tag + ">", StringComparison.InvariantCultureIgnoreCase)));
                }
                else
                {
                    startIndex = -1;
                    endIndex = -1;
                }
            }
            catch
            {
                startIndex = -1;
                endIndex = -1;
            }
        }

        public static T FindDescendant<T>(DependencyObject obj) where T : DependencyObject
        {
            if (obj == null) return default(T);
            int numberChildren = VisualTreeHelper.GetChildrenCount(obj);
            if (numberChildren == 0) return default(T);

            for (int i = 0; i < numberChildren; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child is T)
                {
                    return (T)(object)child;
                }
            }

            for (int i = 0; i < numberChildren; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                var potentialMatch = FindDescendant<T>(child);
                if (potentialMatch != default(T))
                {
                    return potentialMatch;
                }
            }

            return default(T);
        }

        private List<ReplaceRemove> lstLocalReplace { get; set; }

        private List<AddContent> lstLocalAddContnet { get; set; }

        public List<ReplaceRemove> lstReplace
        {
            get
            {
                return (Application.Current.MainWindow as MainWindow).ListReplaceRemove;
            }
        }

        public List<AddContent> LstAddContent
        {
            get
            {
                return (Application.Current.MainWindow as MainWindow).ListAddContent;
            }
        }
    }
}
